library(lubridate)
library(dplyr)
library(tidyr)
library(xts)
library(tseries)
library(forecast)
library(zoo)
options(scipen=999)

In this notebook, we want to analyse whether Trump’s tweets have a statistically significant effect on his approval ratings, and if they do, can we use the popularity of his tweets to predict his approval ratings?

First, import csv data from Kaggle (stored on local machine).

tweets <- read.csv('realdonaldtrump.csv', header=TRUE)
approval <- read.csv('datasets_683990_1200219_trump-approval-ratings_approval_topline.csv', header=TRUE)
head(tweets)
head(approval)

Now, pull out favourites, approval %age estimate & disapproval %age estimate.

tweets_date <- format(as.Date(tweets$date), "%m/%d/%Y")
approval_date <- strftime(as.Date(approval$modeldate, format="%m/%d/%Y"), format="%m/%d/%Y")
approval_df <- data.frame(approval_date, approval$approve_estimate)
disapproval_df <- data.frame(approval_date, approval$disapprove_estimate)
tweets_df <- data.frame(tweets_date, tweets$favorites)
head(disapproval_df)
tail(tweets_df)

Now we need to match these up by date. First, we need to take average of favourites & (dis)approval per day - use dplyr to group by date and take average.

tweets_fav <- tweets_df %>%
  group_by(tweets_date) %>%
  mutate(mean_favorites = mean(tweets.favorites)) %>%
  select(-tweets.favorites) %>%
  distinct()

approval_means <- approval_df %>%
  group_by(approval_date) %>%
  mutate(mean_approval  = mean(approval.approve_estimate)) %>%
  select(-approval.approve_estimate) %>%
  distinct()

disapproval_means <- disapproval_df %>%
  group_by(approval_date) %>%
  mutate(mean_disapproval  = mean(approval.disapprove_estimate)) %>%
  select(-approval.disapprove_estimate) %>%
  distinct()

approval_means_asc = approval_means[order(as.Date(approval_means$approval_date, format = "%m/%d/%Y")),]
disapproval_means_asc = disapproval_means[order(as.Date(approval_means$approval_date, format = "%m/%d/%Y")),]

head(tweets_fav)
head(approval_means_asc)
tail(disapproval_means_asc)

Match up starting dates.

tweets_ts <- tweets_fav[which(tweets_fav$tweets_date == "01/23/2017"):which(tweets_fav$tweets_date == "05/29/2020"),]

approve_ts <- approval_means_asc[which(approval_means_asc$approval_date == "01/23/2017"):which(approval_means_asc$approval_date == "05/29/2020"),]

disapprove_ts <- disapproval_means_asc[which(disapproval_means_asc$approval_date == "01/23/2017"):which(disapproval_means_asc$approval_date == "05/29/2020"),]

Finally, merge into one dataframe.

head(ts_complete)
head(ts_complete)

Create time series objects.

favourites_ts <- ts(ts_complete$mean_favs, start = 1, frequency = 1)
approval_ts <- ts(ts_complete$mean_app, start = 1, frequency = 1)
disapproval_ts <- ts(ts_complete$mean_disapp, start = 1, frequency = 1)

Plot and compare. First, approval ratings.

plot(approval_ts, type="l", col = "green", ylim = c(35,60))
lines(disapproval_ts, type="l", col="red")

Now, favourites against both (just the most recent 365 days).

mar.default <- c(5,4,4,2) + 0.1
par(mar = mar.default + c(0, 0, 0, 4)) 
plot(favourites_ts[844:1209], type="l", col="blue", ylab = "Favourites")
par(new = TRUE)
plot(approval_ts[844:1209], type = "l", axes = FALSE, bty = "n", col="green", xlab = "", ylab = "")
par(new = TRUE)
plot(disapproval_ts[844:1209], type = "l", axes = FALSE, bty = "n", col="red", xlab = "", ylab = "")
axis(side=4)
mtext("Approval percentage", side=4, line = 3)
legend("topleft", c("Favourites","Approval", "Disapproval"), lty = c(1,1), col=c("Blue", "Green", "Red"))

We see some possible correlation, but we need to go deeper.


Now we have our data nicely set up, it’s time to do some analysis. First, carry out a simple regression between favourites and approval. First, check if there is any correlation. First, plot.

plot(ts_complete$mean_favs, ts_complete$mean_app)

Seems like a pretty random spread - now check correlation.

cor(ts_complete$mean_app, ts_complete$mean_favs, method = c("pearson"))
[1] 0.1947603

Slight positive correlation - now check disapproval.

cor(ts_complete$mean_disapp, ts_complete$mean_favs, method = c("pearson"))
[1] -0.1408452
as.numeric(ts_complete$date)[1:5]
[1] 17189 17190 17191 17192 17193

Negative correlation, as exppected. Now we regress approval & disapproval on favourites. Inlcude date as a predictor, as we expect a time trend (note, we subtract 17188 from date so that it starts at 1).

app_lm <-lm(mean_app ~ mean_favs + as.numeric(I(date-17188)), data=ts_complete)
disapp_lm <-lm(mean_disapp ~ mean_favs + as.numeric(I(date-17188)), data=ts_complete)
summary(app_lm)

Call:
lm(formula = mean_app ~ mean_favs + as.numeric(I(date - 17188)), 
    data = ts_complete)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.0401 -0.8551 -0.0090  0.7957  7.4324 

Coefficients:
                                Estimate   Std. Error t value             Pr(>|t|)    
(Intercept)                 39.503592015  0.138457263 285.313 < 0.0000000000000002 ***
mean_favs                    0.000004331  0.000001390   3.115              0.00188 ** 
as.numeric(I(date - 17188))  0.002394455  0.000133424  17.946 < 0.0000000000000002 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.586 on 1206 degrees of freedom
Multiple R-squared:  0.2407,    Adjusted R-squared:  0.2394 
F-statistic: 191.2 on 2 and 1206 DF,  p-value: < 0.00000000000000022
summary(disapp_lm)

Call:
lm(formula = mean_disapp ~ mean_favs + as.numeric(I(date - 17188)), 
    data = ts_complete)

Residuals:
     Min       1Q   Median       3Q      Max 
-12.9555  -0.7971   0.0671   0.9170   4.1050 

Coefficients:
                                Estimate   Std. Error t value             Pr(>|t|)    
(Intercept)                 54.268797601  0.167159784 324.652 < 0.0000000000000002 ***
mean_favs                   -0.000006925  0.000001679  -4.126            0.0000395 ***
as.numeric(I(date - 17188)) -0.000444903  0.000161083  -2.762              0.00583 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.914 on 1206 degrees of freedom
Multiple R-squared:  0.026, Adjusted R-squared:  0.02438 
F-statistic:  16.1 on 2 and 1206 DF,  p-value: 0.0000001263

We see that all covariates are statistically significant in both models.


Now we have shown a relationship between favourites and approval/disapprocal, we want to use ARIMA models to predict twitter favourites, and use this to in turn predict approval ratings.

First, check whether favourites are stationary.

kpss.test(favourites_ts)
p-value smaller than printed p-value

    KPSS Test for Level Stationarity

data:  favourites_ts
KPSS Level = 2.3025, Truncation lag parameter = 7, p-value = 0.01

Significant evidence that this is NOT stationary at 1% level. So, try auto.arima - we expect some differencing to be necessary to give stationarity.

auto.arima(favourites_ts, ic = "aic", seasonal = TRUE, trace = TRUE)

 Fitting models using approximations to speed things up...

 ARIMA(2,1,2) with drift         : 28266.8
 ARIMA(0,1,0) with drift         : 28722.87
 ARIMA(1,1,0) with drift         : 28492.38
 ARIMA(0,1,1) with drift         : 28315.32
 ARIMA(0,1,0)                    : 28720.88
 ARIMA(1,1,2) with drift         : 28257.47
 ARIMA(0,1,2) with drift         : 28286.1
 ARIMA(1,1,1) with drift         : 28267.67
 ARIMA(1,1,3) with drift         : 28257.93
 ARIMA(0,1,3) with drift         : 28275.17
 ARIMA(2,1,1) with drift         : 28265.45
 ARIMA(2,1,3) with drift         : 28269.16
 ARIMA(1,1,2)                    : 28255.61
 ARIMA(0,1,2)                    : 28284.1
 ARIMA(1,1,1)                    : 28265.68
 ARIMA(2,1,2)                    : 28264.95
 ARIMA(1,1,3)                    : 28256.02
 ARIMA(0,1,1)                    : 28313.32
 ARIMA(0,1,3)                    : 28273.18
 ARIMA(2,1,1)                    : 28263.63
 ARIMA(2,1,3)                    : 28267.31

 Now re-fitting the best model(s) without approximations...

 ARIMA(1,1,2)                    : 28279.07

 Best model: ARIMA(1,1,2)                    

Series: favourites_ts 
ARIMA(1,1,2) 

Coefficients:
         ar1      ma1     ma2
      0.6313  -1.3188  0.3471
s.e.  0.0850   0.0964  0.0874

sigma^2 estimated as 854910514:  log likelihood=-14135.53
AIC=28279.07   AICc=28279.1   BIC=28299.45

So, we can use an ARIMA(1,1,2) model to forecast this TS. Use forecast package to do so.

arima_fav <- arima(favourites_ts, order = c(1,1,2), include.mean = T)
fc <- forecast(arima_fav, level = 95)
plot(fc, xlim = c(1200, 1220))

We can use this data, along with our linear model, to predict approval rating from tweet favourites.


LS0tCnRpdGxlOiAiVHJ1bXAgVHdlZXRzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0KbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoeHRzKQpsaWJyYXJ5KHRzZXJpZXMpCmxpYnJhcnkoZm9yZWNhc3QpCmxpYnJhcnkoem9vKQpgYGAKCmBgYHtyfQpvcHRpb25zKHNjaXBlbj05OTkpCmBgYAoKSW4gdGhpcyBub3RlYm9vaywgd2Ugd2FudCB0byBhbmFseXNlIHdoZXRoZXIgVHJ1bXAncyB0d2VldHMgaGF2ZSBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZWZmZWN0IG9uIGhpcyBhcHByb3ZhbCByYXRpbmdzLCBhbmQgaWYgdGhleSBkbywgY2FuIHdlIHVzZSB0aGUgcG9wdWxhcml0eSBvZiBoaXMgdHdlZXRzIHRvIHByZWRpY3QgaGlzIGFwcHJvdmFsIHJhdGluZ3M/CgpGaXJzdCwgaW1wb3J0IGNzdiBkYXRhIGZyb20gS2FnZ2xlIChzdG9yZWQgb24gbG9jYWwgbWFjaGluZSkuCgpgYGB7cn0KdHdlZXRzIDwtIHJlYWQuY3N2KCdyZWFsZG9uYWxkdHJ1bXAuY3N2JywgaGVhZGVyPVRSVUUpCmFwcHJvdmFsIDwtIHJlYWQuY3N2KCdkYXRhc2V0c182ODM5OTBfMTIwMDIxOV90cnVtcC1hcHByb3ZhbC1yYXRpbmdzX2FwcHJvdmFsX3RvcGxpbmUuY3N2JywgaGVhZGVyPVRSVUUpCmhlYWQodHdlZXRzKQpoZWFkKGFwcHJvdmFsKQpgYGAKCk5vdywgcHVsbCBvdXQgZmF2b3VyaXRlcywgYXBwcm92YWwgJWFnZSBlc3RpbWF0ZSAmIGRpc2FwcHJvdmFsICVhZ2UgZXN0aW1hdGUuCgpgYGB7cn0KdHdlZXRzX2RhdGUgPC0gZm9ybWF0KGFzLkRhdGUodHdlZXRzJGRhdGUpLCAiJW0vJWQvJVkiKQphcHByb3ZhbF9kYXRlIDwtIHN0cmZ0aW1lKGFzLkRhdGUoYXBwcm92YWwkbW9kZWxkYXRlLCBmb3JtYXQ9IiVtLyVkLyVZIiksIGZvcm1hdD0iJW0vJWQvJVkiKQphcHByb3ZhbF9kZiA8LSBkYXRhLmZyYW1lKGFwcHJvdmFsX2RhdGUsIGFwcHJvdmFsJGFwcHJvdmVfZXN0aW1hdGUpCmRpc2FwcHJvdmFsX2RmIDwtIGRhdGEuZnJhbWUoYXBwcm92YWxfZGF0ZSwgYXBwcm92YWwkZGlzYXBwcm92ZV9lc3RpbWF0ZSkKdHdlZXRzX2RmIDwtIGRhdGEuZnJhbWUodHdlZXRzX2RhdGUsIHR3ZWV0cyRmYXZvcml0ZXMpCmhlYWQoZGlzYXBwcm92YWxfZGYpCnRhaWwodHdlZXRzX2RmKQpgYGAKCk5vdyB3ZSBuZWVkIHRvIG1hdGNoIHRoZXNlIHVwIGJ5IGRhdGUuIEZpcnN0LCB3ZSBuZWVkIHRvIHRha2UgYXZlcmFnZSBvZiBmYXZvdXJpdGVzICYgKGRpcylhcHByb3ZhbCBwZXIgZGF5IC0gdXNlIGRwbHlyIHRvIGdyb3VwIGJ5IGRhdGUgYW5kIHRha2UgYXZlcmFnZS4KCmBgYHtyfQp0d2VldHNfZmF2IDwtIHR3ZWV0c19kZiAlPiUKICBncm91cF9ieSh0d2VldHNfZGF0ZSkgJT4lCiAgbXV0YXRlKG1lYW5fZmF2b3JpdGVzID0gbWVhbih0d2VldHMuZmF2b3JpdGVzKSkgJT4lCiAgc2VsZWN0KC10d2VldHMuZmF2b3JpdGVzKSAlPiUKICBkaXN0aW5jdCgpCgphcHByb3ZhbF9tZWFucyA8LSBhcHByb3ZhbF9kZiAlPiUKICBncm91cF9ieShhcHByb3ZhbF9kYXRlKSAlPiUKICBtdXRhdGUobWVhbl9hcHByb3ZhbCAgPSBtZWFuKGFwcHJvdmFsLmFwcHJvdmVfZXN0aW1hdGUpKSAlPiUKICBzZWxlY3QoLWFwcHJvdmFsLmFwcHJvdmVfZXN0aW1hdGUpICU+JQogIGRpc3RpbmN0KCkKCmRpc2FwcHJvdmFsX21lYW5zIDwtIGRpc2FwcHJvdmFsX2RmICU+JQogIGdyb3VwX2J5KGFwcHJvdmFsX2RhdGUpICU+JQogIG11dGF0ZShtZWFuX2Rpc2FwcHJvdmFsICA9IG1lYW4oYXBwcm92YWwuZGlzYXBwcm92ZV9lc3RpbWF0ZSkpICU+JQogIHNlbGVjdCgtYXBwcm92YWwuZGlzYXBwcm92ZV9lc3RpbWF0ZSkgJT4lCiAgZGlzdGluY3QoKQoKYXBwcm92YWxfbWVhbnNfYXNjID0gYXBwcm92YWxfbWVhbnNbb3JkZXIoYXMuRGF0ZShhcHByb3ZhbF9tZWFucyRhcHByb3ZhbF9kYXRlLCBmb3JtYXQgPSAiJW0vJWQvJVkiKSksXQpkaXNhcHByb3ZhbF9tZWFuc19hc2MgPSBkaXNhcHByb3ZhbF9tZWFuc1tvcmRlcihhcy5EYXRlKGFwcHJvdmFsX21lYW5zJGFwcHJvdmFsX2RhdGUsIGZvcm1hdCA9ICIlbS8lZC8lWSIpKSxdCgpoZWFkKHR3ZWV0c19mYXYpCmhlYWQoYXBwcm92YWxfbWVhbnNfYXNjKQp0YWlsKGRpc2FwcHJvdmFsX21lYW5zX2FzYykKYGBgCgpNYXRjaCB1cCBzdGFydGluZyBkYXRlcy4KCmBgYHtyfQp0d2VldHNfdHMgPC0gdHdlZXRzX2Zhdlt3aGljaCh0d2VldHNfZmF2JHR3ZWV0c19kYXRlID09ICIwMS8yMy8yMDE3Iik6d2hpY2godHdlZXRzX2ZhdiR0d2VldHNfZGF0ZSA9PSAiMDUvMjkvMjAyMCIpLF0KCmFwcHJvdmVfdHMgPC0gYXBwcm92YWxfbWVhbnNfYXNjW3doaWNoKGFwcHJvdmFsX21lYW5zX2FzYyRhcHByb3ZhbF9kYXRlID09ICIwMS8yMy8yMDE3Iik6d2hpY2goYXBwcm92YWxfbWVhbnNfYXNjJGFwcHJvdmFsX2RhdGUgPT0gIjA1LzI5LzIwMjAiKSxdCgpkaXNhcHByb3ZlX3RzIDwtIGRpc2FwcHJvdmFsX21lYW5zX2FzY1t3aGljaChkaXNhcHByb3ZhbF9tZWFuc19hc2MkYXBwcm92YWxfZGF0ZSA9PSAiMDEvMjMvMjAxNyIpOndoaWNoKGRpc2FwcHJvdmFsX21lYW5zX2FzYyRhcHByb3ZhbF9kYXRlID09ICIwNS8yOS8yMDIwIiksXQpgYGAKCkZpbmFsbHksIG1lcmdlIGludG8gb25lIGRhdGFmcmFtZS4KCmBgYHtyfQp0d2VldHNfem9vIDwtIHpvbyh0d2VldHNfdHMpCmFwcHJvdmVfem9vIDwtIHpvbyhhcHByb3ZlX3RzKQpkaXNhcHByb3ZlX3pvbyA8LSB6b28oZGlzYXBwcm92ZV90cykKCm5hbWVzKHR3ZWV0c196b28pIDwtIGMoJ3R3ZWV0c19kYXRlJywgJ21lYW5fZmF2cycpCm5hbWVzKGFwcHJvdmVfem9vKSA8LSBjKCdhcHBfZGF0ZScsICdtZWFuX2FwcCcpCm5hbWVzKGRpc2FwcHJvdmVfem9vKSA8LSBjKCdkaXNhcHBfZGF0ZScsICdtZWFuX2Rpc2FwcCcpCgphcHByb3ZlX3pvb19kYXRlIDwtIGRhdGEuZnJhbWUoYXBwcm92ZV96b29bKGFwcHJvdmVfem9vJGFwcF9kYXRlICVpbiUgdHdlZXRzX3pvbyR0d2VldHNfZGF0ZSksXSkKZGlzYXBwcm92ZV96b29fZGF0ZSA8LSBkYXRhLmZyYW1lKGRpc2FwcHJvdmVfem9vWyhkaXNhcHByb3ZlX3pvbyRkaXNhcHBfZGF0ZSAlaW4lIHR3ZWV0c196b28kdHdlZXRzX2RhdGUpLF0pCgphcHByb3ZlX3pvb19kYXRlICU+JSBkcm9wX25hKCkKZGlzYXBwcm92ZV96b29fZGF0ZSAlPiUgZHJvcF9uYSgpCgpjb21wbGV0ZV9kZiA8LSBkYXRhLmZyYW1lKG1lcmdlKHR3ZWV0c196b28sIGFwcHJvdmVfem9vX2RhdGUsIGRpc2FwcHJvdmVfem9vX2RhdGUpKQoKdHNfY29tcGxldGUgPC0gY29tcGxldGVfZGYgJT4lCiAgc2VsZWN0KC1hcHBfZGF0ZSkgJT4lCiAgc2VsZWN0KC1kaXNhcHBfZGF0ZSkgJT4lCiAgcmVuYW1lKGRhdGUgPSB0d2VldHNfZGF0ZSkgJT4lCiAgbXV0YXRlKGRhdGUgPSBtZHkoZGF0ZSkpICU+JQogIG11dGF0ZShtZWFuX2ZhdnMgPSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihtZWFuX2ZhdnMpKSkgJT4lCiAgbXV0YXRlKG1lYW5fYXBwID0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIobWVhbl9hcHApKSkgJT4lCiAgbXV0YXRlKG1lYW5fZGlzYXBwID0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIobWVhbl9kaXNhcHApKSkKICAKYGBgCmBgYHtyfQpoZWFkKHRzX2NvbXBsZXRlKQpgYGAKCkNyZWF0ZSB0aW1lIHNlcmllcyBvYmplY3RzLgoKYGBge3J9CmZhdm91cml0ZXNfdHMgPC0gdHModHNfY29tcGxldGUkbWVhbl9mYXZzLCBzdGFydCA9IDEsIGZyZXF1ZW5jeSA9IDEpCmFwcHJvdmFsX3RzIDwtIHRzKHRzX2NvbXBsZXRlJG1lYW5fYXBwLCBzdGFydCA9IDEsIGZyZXF1ZW5jeSA9IDEpCmRpc2FwcHJvdmFsX3RzIDwtIHRzKHRzX2NvbXBsZXRlJG1lYW5fZGlzYXBwLCBzdGFydCA9IDEsIGZyZXF1ZW5jeSA9IDEpCmBgYAoKUGxvdCBhbmQgY29tcGFyZS4gRmlyc3QsIGFwcHJvdmFsIHJhdGluZ3MuCgpgYGB7cn0KcGxvdChhcHByb3ZhbF90cywgdHlwZT0ibCIsIGNvbCA9ICJncmVlbiIsIHlsaW0gPSBjKDM1LDYwKSkKbGluZXMoZGlzYXBwcm92YWxfdHMsIHR5cGU9ImwiLCBjb2w9InJlZCIpCmBgYAoKTm93LCBmYXZvdXJpdGVzIGFnYWluc3QgYm90aCAoanVzdCB0aGUgbW9zdCByZWNlbnQgMzY1IGRheXMpLgoKYGBge3J9Cm1hci5kZWZhdWx0IDwtIGMoNSw0LDQsMikgKyAwLjEKcGFyKG1hciA9IG1hci5kZWZhdWx0ICsgYygwLCAwLCAwLCA0KSkgCnBsb3QoZmF2b3VyaXRlc190c1s4NDQ6MTIwOV0sIHR5cGU9ImwiLCBjb2w9ImJsdWUiLCB5bGFiID0gIkZhdm91cml0ZXMiKQpwYXIobmV3ID0gVFJVRSkKcGxvdChhcHByb3ZhbF90c1s4NDQ6MTIwOV0sIHR5cGUgPSAibCIsIGF4ZXMgPSBGQUxTRSwgYnR5ID0gIm4iLCBjb2w9ImdyZWVuIiwgeGxhYiA9ICIiLCB5bGFiID0gIiIpCnBhcihuZXcgPSBUUlVFKQpwbG90KGRpc2FwcHJvdmFsX3RzWzg0NDoxMjA5XSwgdHlwZSA9ICJsIiwgYXhlcyA9IEZBTFNFLCBidHkgPSAibiIsIGNvbD0icmVkIiwgeGxhYiA9ICIiLCB5bGFiID0gIiIpCmF4aXMoc2lkZT00KQptdGV4dCgiQXBwcm92YWwgcGVyY2VudGFnZSIsIHNpZGU9NCwgbGluZSA9IDMpCmxlZ2VuZCgidG9wbGVmdCIsIGMoIkZhdm91cml0ZXMiLCJBcHByb3ZhbCIsICJEaXNhcHByb3ZhbCIpLCBsdHkgPSBjKDEsMSksIGNvbD1jKCJCbHVlIiwgIkdyZWVuIiwgIlJlZCIpKQpgYGAKCldlIHNlZSBzb21lIHBvc3NpYmxlIGNvcnJlbGF0aW9uLCBidXQgd2UgbmVlZCB0byBnbyBkZWVwZXIuCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCk5vdyB3ZSBoYXZlIG91ciBkYXRhIG5pY2VseSBzZXQgdXAsIGl0J3MgdGltZSB0byBkbyBzb21lIGFuYWx5c2lzLiBGaXJzdCwgY2Fycnkgb3V0IGEgc2ltcGxlIHJlZ3Jlc3Npb24gYmV0d2VlbiBmYXZvdXJpdGVzIGFuZCBhcHByb3ZhbC4gRmlyc3QsIGNoZWNrIGlmIHRoZXJlIGlzIGFueSBjb3JyZWxhdGlvbi4gRmlyc3QsIHBsb3QuCgpgYGB7cn0KcGxvdCh0c19jb21wbGV0ZSRtZWFuX2ZhdnMsIHRzX2NvbXBsZXRlJG1lYW5fYXBwKQpgYGAKClNlZW1zIGxpa2UgYSBwcmV0dHkgcmFuZG9tIHNwcmVhZCAtIG5vdyBjaGVjayBjb3JyZWxhdGlvbi4KCmBgYHtyfQpjb3IodHNfY29tcGxldGUkbWVhbl9hcHAsIHRzX2NvbXBsZXRlJG1lYW5fZmF2cywgbWV0aG9kID0gYygicGVhcnNvbiIpKQpgYGAKClNsaWdodCBwb3NpdGl2ZSBjb3JyZWxhdGlvbiAtIG5vdyBjaGVjayBkaXNhcHByb3ZhbC4KCmBgYHtyfQpjb3IodHNfY29tcGxldGUkbWVhbl9kaXNhcHAsIHRzX2NvbXBsZXRlJG1lYW5fZmF2cywgbWV0aG9kID0gYygicGVhcnNvbiIpKQpgYGAKCmBgYHtyfQphcy5udW1lcmljKHRzX2NvbXBsZXRlJGRhdGUpWzE6NV0KYGBgCgpOZWdhdGl2ZSBjb3JyZWxhdGlvbiwgYXMgZXhwcGVjdGVkLiBOb3cgd2UgcmVncmVzcyBhcHByb3ZhbCAmIGRpc2FwcHJvdmFsIG9uIGZhdm91cml0ZXMuIElubGN1ZGUgZGF0ZSBhcyBhIHByZWRpY3RvciwgYXMgd2UgZXhwZWN0IGEgdGltZSB0cmVuZCAobm90ZSwgd2Ugc3VidHJhY3QgMTcxODggZnJvbSBkYXRlIHNvIHRoYXQgaXQgc3RhcnRzIGF0IDEpLgoKYGBge3J9CmFwcF9sbSA8LWxtKG1lYW5fYXBwIH4gbWVhbl9mYXZzICsgYXMubnVtZXJpYyhJKGRhdGUtMTcxODgpKSwgZGF0YT10c19jb21wbGV0ZSkKZGlzYXBwX2xtIDwtbG0obWVhbl9kaXNhcHAgfiBtZWFuX2ZhdnMgKyBhcy5udW1lcmljKEkoZGF0ZS0xNzE4OCkpLCBkYXRhPXRzX2NvbXBsZXRlKQpzdW1tYXJ5KGFwcF9sbSkKc3VtbWFyeShkaXNhcHBfbG0pCmBgYAoKV2Ugc2VlIHRoYXQgYWxsIGNvdmFyaWF0ZXMgYXJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgaW4gYm90aCBtb2RlbHMuCgotLS0tLS0tLS0tLS0tLS0tCgpOb3cgd2UgaGF2ZSBzaG93biBhIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGZhdm91cml0ZXMgYW5kIGFwcHJvdmFsL2Rpc2FwcHJvY2FsLCB3ZSB3YW50IHRvIHVzZSBBUklNQSBtb2RlbHMgdG8gcHJlZGljdCB0d2l0dGVyIGZhdm91cml0ZXMsIGFuZCB1c2UgdGhpcyB0byBpbiB0dXJuIHByZWRpY3QgYXBwcm92YWwgcmF0aW5ncy4KCkZpcnN0LCBjaGVjayB3aGV0aGVyIGZhdm91cml0ZXMgYXJlIHN0YXRpb25hcnkuCgpgYGB7cn0Ka3Bzcy50ZXN0KGZhdm91cml0ZXNfdHMpCmBgYAoKU2lnbmlmaWNhbnQgZXZpZGVuY2UgdGhhdCB0aGlzIGlzIE5PVCBzdGF0aW9uYXJ5IGF0IDElIGxldmVsLiBTbywgdHJ5IGF1dG8uYXJpbWEgLSB3ZSBleHBlY3Qgc29tZSBkaWZmZXJlbmNpbmcgdG8gYmUgbmVjZXNzYXJ5IHRvIGdpdmUgc3RhdGlvbmFyaXR5LgoKYGBge3J9CmF1dG8uYXJpbWEoZmF2b3VyaXRlc190cywgaWMgPSAiYWljIiwgc2Vhc29uYWwgPSBUUlVFLCB0cmFjZSA9IFRSVUUpCmBgYAoKU28sIHdlIGNhbiB1c2UgYW4gQVJJTUEoMSwxLDIpIG1vZGVsIHRvIGZvcmVjYXN0IHRoaXMgVFMuIFVzZSBmb3JlY2FzdCBwYWNrYWdlIHRvIGRvIHNvLgoKYGBge3J9CmFyaW1hX2ZhdiA8LSBhcmltYShmYXZvdXJpdGVzX3RzLCBvcmRlciA9IGMoMSwxLDIpLCBpbmNsdWRlLm1lYW4gPSBUKQpmYyA8LSBmb3JlY2FzdChhcmltYV9mYXYsIGxldmVsID0gOTUpCnBsb3QoZmMsIHhsaW0gPSBjKDEyMDAsIDEyMjApKQpgYGAKCldlIGNhbiB1c2UgdGhpcyBkYXRhLCBhbG9uZyB3aXRoIG91ciBsaW5lYXIgbW9kZWwsIHRvIHByZWRpY3QgYXBwcm92YWwgcmF0aW5nIGZyb20gdHdlZXQgZmF2b3VyaXRlcy4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKCgo=